/*
 *	Copyright (c) 2009, libBasement
 *	All rights reserved.
 *	
 *	Redistribution and use in source and binary forms, with or without modification,
 *	are permitted provided that the following conditions are met:
 *	
 *	Redistributions of source code must retain the above copyright notice,
 *	this list of conditions and the following disclaimer.
 *	Redistributions in binary form must reproduce the above copyright notice,
 *	this list of conditions and the following disclaimer in the documentation
 *	and/or other materials provided with the distribution.
 *	Neither the name of the <ORGANIZATION> nor the names of its contributors may
 *	be used to endorse or promote products derived from this software without
 *	specific prior written permission.
 *	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 *	THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *	ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 *	FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *	(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *	LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *	ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *	(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "BString.h"
#include "BException.h"
#include <cstring>
#include <stdarg.h>

namespace Basement
{
	static char *const EmptyStorage = new char [0];
	
	String *$(String *string)
	{
		return String::withString(string);
	}
	
	String *$(const char *cString)
	{
		return String::withCString(cString);
	}
	
	String *$s(String *string)
	{
		return String::alloc()->initWithString(string);
	}
	
	String *$s(const char *cString)
	{
		return String::alloc()->initWithCString(cString);
	}
	
#pragma mark -
	
	String *String::init()
	{
		if(Object::init())
		{
			_string = std::string();
			
			return this;
		}
		
		return NULL;
	}
	
#pragma mark -
	
	String *String::withString(String *other)
	{
		return String::alloc()->initWithString(other)->autorelease< String * >();
	}
	
	String *String::withStdString(std::string &string)
	{
		return String::alloc()->initWithStdString(string);
	}
	
	String *String::withCString(const char *cString)
	{
		return String::alloc()->initWithCString(cString)->autorelease< String * >();
	}
	
	String *String::withStrings(String *string, ...)
	{
		va_list arguments;
		va_start(arguments, string);
		
		String *result = String::alloc()->initWithStrings(string, arguments);
		
		va_end(arguments);
		
		return result->autorelease< String * >();
	}
	
#pragma mark -
	
	String *String::initWithString(String *other)
	{
		if(this->initWithStdString(other->_string))
		{
			return this;
		}
		
		return NULL;
	}
	
	String *String::initWithStdString(std::string &string)
	{
		if(this->init())
		{
			_string = string;
			
			return this;
		}
		return NULL;
	}
	
	String *String::initWithCString(const char *cString)
	{
		if(this->init())
		{
			_string.append(cString);
			
			return this;
		}
		
		return NULL;
	}
	
#pragma mark -
	
	String *String::initWithStrings(String *string, ...)
	{
		va_list arg;
		va_start(arg, string);
		
		String *result = this->initWithStrings(string, arg);
		
		va_end(arg);
		
		return result;
	}
	
	String *String::initWithStrings(String *string, va_list arguments)
	{
		if(this->init())
		{
			this->appendString(string);
			
			Object *ptr = va_arg(arguments, Object *);
			while (ptr != NULL)
			{
				this->appendString(ptr->description());
				ptr = va_arg(arguments, Object *);
			}
			
			return this;
		}
		
		return NULL;
	}
	
#pragma mark Accessors
	
	UInteger String::length() const
	{
		return _string.length();
	}
	
	char String::characterAtIndex(UInteger index) const
	{
		BAssert((index < this->length()), $("Index greater then length"));
		return _string[index];
	}
	
	const char *String::getCString() const
	{
		return _string.c_str();
	}
	
	std::string String::getStdString() const
	{
		return _string;
	}
	
#pragma mark -
	
	char *String::getCharactersInRange(Range range) const
	{
		std::string string = _string.substr(range.location, range.length);
		
		char *storage = new char[string.size()];
		if(!strcpy(storage, string.c_str()))
		{
			delete [] storage;
			Exception::withNameAndReason(InternalInconsistencyException, $("Could not copy string into new storage."))->raise();
			
			return NULL;
		}
		
		return storage;
	}
	
	char *String::copyCString() const
	{
		return this->getCharactersInRange(Range(0, this->length()));
	}
	
#pragma mark Appension
	
	String *String::stringByAppendingString(String *otherString) const
	{
		BParamaterAssert(otherString);
		
		String *copy = String::withStdString((std::string &)_string);
		copy->appendString(otherString);
		
		return copy;
	}
	
	String *String::appendString(String *otherString)
	{
		BParamaterAssert(otherString);
		
		_string.append(otherString->_string);
		
		return this;
	}
	
#pragma mark Character Replacement
	
	String *String::replaceCharactersInRange(Range range, String *otherString)
	{
		BParamaterAssert(otherString);
		
		_string.replace(range.location, range.length, otherString->_string);
		
		return this;
	}
	
	String *String::deleteCharactersInRange(Range range)
	{
		this->replaceCharactersInRange(range, $(""));
		return this;
	}
	
	String *String::replaceOccurrencesOfStringWithString(String *needle, String *replacement)
	{
		std::string nativeNeedle = needle->getStdString();
		std::string nativeReplacement = replacement->getStdString();
		UInteger offset = 0;
		while ((offset = _string.find(nativeNeedle, offset)) != std::string::npos)
		{
			_string.replace(offset, nativeNeedle.size(), nativeReplacement);
		}
		
		return this;
	}
	
#pragma mark Substrings
	
	String *String::substringWithRange(Range range) const
	{
		std::string result = _string.substr(range.location, range.length);
		return String::withStdString(result);
	}
	
	String *String::substringToIndex(UInteger index) const
	{
		return this->substringWithRange(Range(0, index));
	}
	
	String *String::substringFromIndex(UInteger index) const
	{
		return this->substringWithRange(Range(index, this->length() - index));
	}
	
#pragma mark Equality
	
	bool String::isEqualToString(String *other)
	{
		BParamaterAssert(other);
		return _string == other->_string;
	}
	
	bool String::hasPrefix(String *other) const
	{
		BParamaterAssert(other);
		if(other->length() <= this->length())
		{
			for (UInteger index = 0; index < other->length(); index++)
			{
				if(this->characterAtIndex(index) != other->characterAtIndex(index))
				{
					return false;
				}
			}
		}
		else
		{
			return false;
		}
		return true;
	}
	
	bool String::hasSuffix(String *other) const
	{
		BParamaterAssert(other);
		if(other->length() <= this->length())
		{
			UInteger difference = this->length() - other->length();
			for (Integer index = other->length() - 1; index >= 0; index--)
			{
				if(this->characterAtIndex(difference + index) != other->characterAtIndex(index))
				{
					return false;
				}
			}
		}
		else
		{
			return false;
		}
		return true;
	}
}
